mod shape;

use itertools::Itertools;
// use log::info;
use shape::{ALL_SHAPE, LAIZI_INDEX, PART_INDEX};
use std::collections::HashSet;
use std::ops::{Index, IndexMut};
use TileType::*;

pub fn search_laizi_bu(key: u64) -> bool {
    LAIZI_INDEX.contains_key(&key)
}

pub fn search_part(key: u64) -> bool {
    PART_INDEX.contains_key(&key)
}

pub fn search_all_shape(key: u64) -> bool {
    ALL_SHAPE.contains(&key)
}

#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TileType {
    WAN,
    TIAO,
    TONG,
    FENG,
    JIAN,
    ZI,
    NOZI,
    ALL,
    INVAILD,
}

#[repr(C)]
#[derive(Debug, Clone)]
pub struct Counter {
    pub t: TileType,
    pub n: u8,
    // 存放某个点数的牌有几张
    pub bucket: [u8; 40],
    pub bucket_len: u8,
    //tcount[n]:包含n张牌的有几个,
    //例如：2,3,4,5,5,5,5,6,6 的tcount是
    //0, 3(3个单张), 1(1个对子), 0, 1(1个杠)...
    //tcount[0]空缺无意义
    //一般最多4张牌，留5个位置就够，保留16个位置是
    //为了适应癞子等特殊需求逻辑
    pub tcount: [u8; 16],
}

impl Counter {
    fn new(mtt: TileType) -> Self {
        let bucket_len: u8 = match mtt {
            // 1..9
            WAN => 9,
            // 11..19
            TIAO => 9,
            // 21..29
            TONG => 9,
            // 31,32,33,34
            FENG => 4,
            // 35,36,37
            JIAN => 3,
            // 31..37
            ZI => 7,
            NOZI => 29,
            ALL => 37,
            _ => 0,
        };
        Self {
            t: mtt,
            n: 0,
            bucket: [0; 40],
            bucket_len,
            tcount: [0; 16],
        }
    }

    fn reset(&mut self) {
        self.n = 0;
        let _ = &self.bucket[..].fill(0);
        let _ = &self.tcount[..].fill(0);
    }

    fn add(&mut self, ti: u8, one: u8) {
        self.n += 1;
        let ti = ti as usize;
        let bktidx: usize = match self.t {
            WAN | TIAO | TONG | ZI | FENG => ti % 10 - 1,
            JIAN => ti % 10 - 5,
            NOZI => ti - 1,
            ALL => ti - 1,
            _ => 0,
        };
        self.bucket[bktidx] += one;
    }

    //返回牌型数据，用1个0分割断开的牌
    //例如：AADDDEF -> vector(2,0,0,3,1,1,0,0,0)
    //返回：20311
    fn calc_shape(&self) -> u64 {
        let mut s: u64 = 0;
        let limit: usize = self.bucket_len as usize - 1;
        for i in 0..self.bucket_len as usize {
            let c = self.bucket[i];
            if c != 0 || (i < limit && self.bucket[i + 1] != 0) {
                s = s * 10 + c as u64;
            }
        }
        s
    }

    //把统计个数的桶，连接起来，转成一个十进制整数
    //例如：AADDDEF -> vector(2,0,0,3,1,1,0,0,0)
    //返回：200311000
    fn calc_bu_shape(&self) -> u64 {
        let mut s: u64 = 0;
        for i in 0..self.bucket_len as usize {
            let c = self.bucket[i];
            s = s * 10 + c as u64;
        }
        s
    }

    fn calc_tcount(&mut self) {
        for i in 0..self.bucket_len as usize {
            if self.bucket[i] != 0 {
                self.tcount[self.bucket[i] as usize] += 1;
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct MJTiles {
    pub tiles: [u8; 32],
    pub tiles_len: u8,
    pub laizi: HashSet<u8>,
    pub laizi_count: u8,
    pub counters: [Counter; 8],
    pub shape_z: u64,
    pub shape_noz: u64,
}

impl Index<TileType> for MJTiles {
    type Output = Counter;
    fn index(&self, index: TileType) -> &Self::Output {
        &self.counters[index as usize]
    }
}

impl IndexMut<TileType> for MJTiles {
    fn index_mut(&mut self, index: TileType) -> &mut Self::Output {
        &mut self.counters[index as usize]
    }
}

impl MJTiles {
    pub fn new() -> Self {
        Self {
            tiles: [0; 32],
            tiles_len: 0,
            laizi: HashSet::new(),
            laizi_count: 0,
            counters: [
                Counter::new(WAN),
                Counter::new(TIAO),
                Counter::new(TONG),
                Counter::new(FENG),
                Counter::new(JIAN),
                Counter::new(ZI),
                Counter::new(NOZI),
                Counter::new(ALL),
            ],
            shape_z: 0,
            shape_noz: 0,
        }
    }

    pub fn assign(&mut self, vtile: &[u8], vlaizi: &[u8]) -> Result<(), String> {
        let tl = vtile.len();
        if tl > 18 || tl < 1 {
            return Err(String::from("invaild tile length"));
        }
        for i in 0..tl {
            self.tiles[i] = vtile[i];
        }
        self.tiles_len = tl as u8;
        for i in 0..vlaizi.len() {
            self.laizi.insert(vlaizi[i]);
        }
        for i in 0..8usize {
            self.counters[i].reset();
        }
        self.count_tiles(&1)?;
        self.shape_z = self[ZI].calc_shape();
        self.shape_noz = self[NOZI].calc_shape();
        Ok(())
    }

    pub fn find_tile(&self, t: u8) -> bool {
        for i in 0..self.tiles_len {
            if t == self.tiles[i as usize] {
                return true;
            }
        }
        false
    }

    pub fn is_lai_zi(&self, tile: u8) -> bool {
        self.laizi.contains(&tile)
    }

    fn get_tile_type(t: &u8) -> Result<TileType, String> {
        match t {
            1..=9 => return Ok(WAN),
            11..=19 => return Ok(TIAO),
            21..=29 => return Ok(TONG),
            31..=34 => return Ok(FENG),
            35..=37 => return Ok(JIAN),
            _ => return Err(String::from("invaild tile")),
        }
    }

    fn count_tiles(&mut self, one: &u8) -> Result<(), String> {
        self.laizi_count = 0;

        for i in 0..self.tiles_len {
            let t = self.tiles[i as usize];
            let mt = MJTiles::get_tile_type(&t)?;
            if self.laizi.contains(&t) {
                self.laizi_count += one;
            }
            self[mt].add(t, *one);
            self[ALL].add(t, *one);
            match mt {
                WAN | TIAO | TONG => {
                    self[NOZI].add(t, *one);
                }
                FENG | JIAN => {
                    self[ZI].add(t, *one);
                }
                _ => (),
            }
        }
        self[ALL].calc_tcount();
        self[NOZI].calc_tcount();
        Ok(())
    }

    pub fn get_hands(&self, hands: &mut Vec<u8>, filter: u8) {
        let mut first = true;
        for i in 0..self.tiles_len as usize {
            if self.tiles[i] != filter {
                hands.push(self.tiles[i])
            } else {
                if !first {
                    hands.push(self.tiles[i]);
                }
                first = false;
            }
        }
    }

    pub fn get_hands_nozi(&self, hands: &mut Vec<u8>, filter: u8) {
        for i in 0..self.tiles_len as usize {
            if self.tiles[i] != filter && self.tiles[i] != 35 {
                hands.push(self.tiles[i])
            } 
        }
    }

    fn no_split_shape(&self, s: u64, lzn: i8, jiang: bool) -> (bool, u8, bool) {
        let mut part_hu = false;

        for i in 0..lzn + 1 {
            let ui = i as u64;
            if !search_laizi_bu((ui << 60) + s) {
                continue;
            }
            let have_jiang = (s + ui % 3) == 2;
            if !(jiang && have_jiang) {
                part_hu = true;
                return (part_hu, ui as u8, jiang || have_jiang);
            }
        }
        (part_hu, 0, jiang)
    }

    fn check_shape_com_hu(&self, vs: &[u64], lzn: u8, jiang: bool) -> (bool, u8, bool) {
        let mut lzx: u8 = 0;
        let mut part_hu = true;
        let mut nj = jiang;

        for s in vs.iter() {
            if search_part(*s) {
                let shape_jiang = *s % 3 == 2;
                if !(shape_jiang && nj) {
                    nj = nj || shape_jiang;
                    continue;
                }
            }
            if *s == 4 && !nj {
                lzx += 1;
                nj = true;
                continue;
            }
            let (ph, u, j) = self.no_split_shape(*s, lzn as i8 - lzx as i8, nj);
            part_hu = ph;
            nj = j;
            if !ph {
                break;
            }
            lzx += u;
        }
        (part_hu, lzx, nj)
    }

    fn last_split_shape(&self, s: u64, lzn: u8, jiang: bool) -> Vec<(bool, u8, bool)> {
        let mut res: Vec<(bool, u8, bool)> = vec![];
        let mut zero_l: Vec<u8> = vec![];
        let ss = format!("{}", s);
        for (i, c) in ss.chars().enumerate() {
            if c == '0' {
                zero_l.push(i as u8);
            }
        }
        // info!("last split shape..shape={}, zero_l={:?}", s, zero_l);
        let mut shape_com: Vec<u64> = vec![];
        let mut zero_com: Vec<u8> = vec![];
        for i in 0..zero_l.len() + 1 {
            for cc in zero_l.iter().combinations(i) {
                zero_com.clear();
                for com in cc {
                    zero_com.push(*com);
                }
                // info!("............................{:?}", zero_com);
                if shape_com.len() != 0 {
                    res.push(self.check_shape_com_hu(&shape_com, lzn, jiang));
                }
                shape_com.clear();
                if zero_com.len() == 0 {
                    shape_com.push(s);
                } else {
                    let ss = format!("{}", s);
                    for k in 0..zero_com.len() {
                        let start: usize = if k == 0 {
                            0
                        } else {
                            zero_com[k - 1] as usize + 1
                        };
                        // info!("SS..............{}.{}", start, zero_com[k] as usize + 1);
                        shape_com
                            .push(ss[start..zero_com[k] as usize + 1].parse::<u64>().unwrap());
                        if zero_com.len() == k + 1 {
                            shape_com.push(ss[zero_com[k] as usize + 1..].parse::<u64>().unwrap());
                        }
                    }
                }
            }
        }
        for i in 0..shape_com.len() {
            let c = [&shape_com[i..], &shape_com[..i]].concat();
            res.push(self.check_shape_com_hu(&c, lzn, jiang));
        }
        res
    }

    fn split_00_shape(&self, s: u64, lzn: &mut u8, jiang: &mut bool) -> bool {
        let mut lzx: u8 = 0;
        let ss = format!("{}", s);
        let mut parthu = false;
        for _sstr in ss.split("00") {
            let s_shape: u64 = ss.parse::<u64>().unwrap();
            let mut lzu: u8 = *lzn - lzx;
            parthu = false;
            let lv = self.last_split_shape(s_shape, lzu, *jiang);
            for l in lv {
                let (px, lx, jx) = l;
                if !px {
                    continue;
                }
                if lx <= lzu {
                    parthu = px;
                    lzu = lx;
                    *jiang = jx;
                }
            }
            lzx += lzu;
            if !parthu {
                break;
            }
            //println!("{}", sstr);
        }
        *lzn = *lzn - lzx;
        parthu
    }

    fn part_bu_shape(&self, s: u64, lzn: &mut u8, jiang: &mut bool) -> bool {
        if search_part(s) {
            let have_jiang = s % 3 == 2;
            if !(*jiang && have_jiang) {
                *lzn = *lzn;
                *jiang = *jiang || have_jiang;
                return true;
            }
        }
        return self.split_00_shape(s, lzn, jiang);
    }

    pub fn is_hu(&mut self) -> bool {
        if self.laizi_count == 0 {
            let mut zicount2: u8 = 0;
            for i in 0..self[ZI].bucket_len {
                let t: u8 = self[ZI].bucket[i as usize];
                if t == 2 {
                    zicount2 += 1;
                }
                if !(t == 0 || t == 2 || t == 3) {
                    return false;
                }
            }
            let mut s: u64 = self.shape_noz;
            if self.shape_z != 0 {
                if zicount2 > 1 {
                    return false;
                }
                s = format!("{}0{}", self.shape_noz, self.shape_z)
                    .parse::<u64>()
                    .unwrap();
            }

            return search_all_shape(s);
        } else {
            let mut lzn: u8 = self.laizi_count;
            if self.tiles_len % 3 != 2 {
                return false;
            }
            let mut jiang: bool = false;
            for men in &[WAN, TIAO, TONG] {
                let mut s: u64 = self[*men].calc_bu_shape();
                for _i in 0..9 {
                    match s % 10 {
                        0 => s /= 10,
                        _ => break,
                    }
                }
                if s == 0 {
                    continue;
                }
                //println!("{}", s);
                if !self.part_bu_shape(s, &mut lzn, &mut jiang) {
                    return false;
                }
            }
            return true;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_shape() {
        let mut mt = MJTiles::new();
        let ts: Vec<u8> = vec![1, 2, 3, 35, 35];
        let lz: Vec<u8> = vec![35];
        mt.assign(&ts, &lz);
        println!("mt.counters: {:?}", mt.counters);
        println!("search_laizi_bu.1 {}", search_laizi_bu(1));
        println!("search_part.1 {}", search_part(1));
        println!("is_hu=={}", mt.is_hu());
    }

    #[test]
    fn it_works() {
        search_laizi_bu(1);
        test_shape();
        assert_eq!(2 + 2, 4);
    }
}
